package modules.dimensioning.solution;

import java.util.Stack;

import javax.naming.directory.InvalidAttributesException;

import org.geotools.geometry.jts.JTSFactoryFinder;

import tools.gis.GisTools;
import tools.math.MathTools;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;

import datatypes.Antenna;

/**
 * Kern van de heristiek: voorstelling van een mogelijke deeloplossing
 * 
 * 
 * @author Bart Moens
 */
public class PartialSolution implements Comparable<PartialSolution>{
	
	GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( null );
	
	//lijst antennes
	protected Antenna[]  				antennas;
	
	//datastructuur vd geometrieen
	protected Geometry[][] 				geometry;
	protected int 						antennaslength;
	protected int 						loglevels ;

	//one undo-move
	protected UndoMove 					undoMove = null;
	
	//SolutionCoverage
	protected Geometry   				total_coverage;
	protected Geometry 					areaToCover;
	
	//fitnesses
	protected double					coverageFitness;
	protected double					costFitness;

	//Parent-solution
	protected Solution 					parent;

	/**
	 * Creeert een mogelijke oplossing.   
	 * 
	 * @param antennas array van antennes, deze worden gecopieerd!
	 * @param areaToCover Doelgebied
	 * @param parent De solution waartoe deze partialSolution behoort
	 * @throws InvalidAttributesException
	 */
	public PartialSolution(Antenna[] antennaList, Geometry areaToCover, Solution parent) throws InvalidAttributesException {

		antennaslength = antennaList.length;
		this.antennas = new Antenna[antennaslength];
		this.areaToCover = areaToCover;
		this.parent = parent;
		
		//init van de gemetry datastructuur
		loglevels = MathTools.log2ceil(antennaslength)+1;		
		geometry = new Geometry[loglevels][antennaslength+1]; //+1 voor nullpointers te voorkomen.  In toekomst macht van 2 prolly
	
		//instellen van de antennes
		for (int i = 0; i < antennaslength; i++) {
			antennas[i] = antennaList[i].clone();
			antennas[i].setMaximum_output_db(parent.getLegislationInterface().getMaximumAntennaPower(antennas[i]));
			antennas[i].setRange(parent.getCoverageInterface().getRangeRadius(antennas[i]));
			
			
			geometry[0][i] 		 = null;
			if (antennas[i].getActive())  
				geometry[0][i] = parent.getCoverageInterface().getCoverage(antennas[i]);
			parent.addChangedAntenna(antennas[i]);
		}
		
		
		// opvullen van de geometry-datastruct
		int elements = antennaslength;
		geometry[loglevels-1][0] = null;
		for (int i = 1; i < loglevels; i++) {			
			elements = (int)Math.ceil(elements/2d); 
			for (int j = 0; j < elements; j++) {				
				geometry[i][j] = GisTools.mergeGeometry(geometry[i-1][2*j], geometry[i-1][(2*j)+1]);
			}
		}	
		
		//coverageFitness berkenen
		total_coverage = geometry[loglevels-1][0];
		if (total_coverage == null) coverageFitness = 0;
		else {
			Geometry b = areaToCover.intersection(total_coverage);
			//b is nu het gecoverde gebied in het te coveren gebied
			coverageFitness = 100* b.getArea()/areaToCover.getArea();
		}
	}
	
	/**
	 * Private constructor nodig voor de clone operatie
	 */
	private PartialSolution() {
	}


	
	/**
	 * Change an antenna from active to disabled or vica versa
	 * 
	 * @param i		antennanumber
	 * @param active status
	 * @throws InvalidAttributesException
	 */
	public void setActiveAntenna(int i, boolean active) throws InvalidAttributesException{
		if (antennas[i].getActive() == active) {
			undoMove = null;
			return;
		}		
		undoMove = new UndoMove(i,antennas[i].getActive(), coverageFitness,total_coverage,antennas[i].getRange());
		antennas[i].setActive(active);
		updateAntenna(i);
		parent.addChangedAntenna(antennas[i]);
	}
	
		
	/**
	 * Update antenna: recalculate the coverage and fitness
	 * 
	 * @param a antennanumber
	 * @throws InvalidAttributesException
	 */
	private void updateAntenna(int a) throws InvalidAttributesException {
		undoMove.addTableMove(a, 0, geometry[0][a]);
		
		if (antennas[a].getActive()) {
			geometry[0][a] 	= parent.getCoverageInterface().getCoverage(antennas[a]);
			antennas[a].setRange(parent.getCoverageInterface().getRangeRadius(antennas[a]));
		} else {
			geometry[0][a] = null;
			antennas[a].setRange(0);
		}
		
		a = a - (a%2);
		int index = a;
		for (int level = 1; level < loglevels; level++) {			
			index = (int)Math.floor(index/2d); 
			undoMove.addTableMove(index, level, geometry[level][index]);
			geometry[level][index] = GisTools.mergeGeometry(geometry[level-1][2*index], geometry[level-1][(2*index)+1]);
		}	
		
		total_coverage = geometry[loglevels-1][0];
		if (total_coverage == null) coverageFitness = 0;
		else {
			Geometry b = areaToCover.intersection(total_coverage);
			//b is nu het gecoverde gebied in het te coveren gebied
			coverageFitness = 100* b.getArea()/areaToCover.getArea();
		}
	}
	
	/**
	 * Fallback to previous solution
	 * @return false if no undo move was found
	 */
	public boolean undo() {
		if (undoMove != null) {
			undoMove.undo();
			undoMove = null;
			return true;
		}
		return false;
	}


	/*** FITNESS CALCULATIONS **/

	/**
	 * Coverage fitness
	 * [0 - 100]
	 */
	public double getCoverageFitnessValue() {
		return coverageFitness;
	}


	/**
	 * Cost Fitness
	 * [0-100]
	 * @return
	 */
	public double getCostFitnessValue() {
		//TODO voor speedup: variabele bij houden
		double actives = 0;
		double activatable = 0;
		for (int i = 0; i < antennaslength; i++) {
			if (!antennas[i].isOperatorSite()) {
				if (antennas[i].getActive()) {
					actives++;
				}
				activatable++;
			}
		}		
		costFitness = ( 100 - (100*actives/activatable));
		return costFitness;
	}

	public double getFitnessValue() {		
		double a = getCoverageFitnessValue();
		return a + parent.getCostFactor(a)*getCostFitnessValue();
	}
	
	
	public boolean getActive(int i) {
		return (antennas[i].getActive()) ;
	}
	
	
	
	public String toString(){
		int s = 0;
		for (int i = 0; i < antennaslength; i++)
			if (antennas[i].getActive())  s++;
		return 	"Fit: " + (float)getFitnessValue() + 
				", Cov: " + (float)getCoverageFitnessValue()+
				"%,  Act: " + (float)(getCostFitnessValue())+
				"% (="+s+" ant)";
	}
	

    public int compareTo(PartialSolution s) {
    	return (int)(1000*getFitnessValue() - 1000*s.getFitnessValue());
    }
	

	public int getNumberOfTotalAntennasOperator() {
		int c = 0;
		for (int i = 0; i < antennaslength; i++) {
			if (antennas[i].isOperatorSite()) {
				c++;
			}
		}	
		return c;
	}
	public int getNumberOfTotalAntennasNotOperator() {
		int c = 0;
		for (int i = 0; i < antennaslength; i++) {
			if (!antennas[i].isOperatorSite()) {
				c++;
			}
		}	
		return c;
	}
	
	public int getNumberOfActiveAntennasOperator() {
		int c = 0;
		for (int i = 0; i < antennaslength; i++) {
			if (antennas[i].isOperatorSite()) {
				if (antennas[i].getActive()) {
					c++;
				}
			}
		}	
		return c;
	}

	public int getNumberOfActiveAntennasNonOperator() {
		int c = 0;
		for (int i = 0; i < antennaslength; i++) {
			if (!antennas[i].isOperatorSite()) {
				if (antennas[i].getActive()) {
					c++;
				}
			}
		}	
		return c;
	}


	public boolean isOperatorSite(int i) {
		// TODO Auto-generated method stub
		return antennas[i].isOperatorSite();
	}

	public int getNumberOfActiveAntennas() {
		int s = 0;
		for (int i = 0; i < antennaslength; i++)
			if (antennas[i].getActive())  s++;
		return s;
	}
	
    public Geometry getAreaToCover() {
    	return areaToCover;
    }
    
    public Antenna[] getAntennas() {
        return antennas;
    }

    public void setAntennas(Antenna[] antennas) {
        this.antennas = antennas;
    }


    public Geometry getTotalGeometry() {
        return geometry[loglevels-1][0];
    }

    public Geometry[][] getGeometry() {
        return geometry;
    }
	
	/**
	 * Clonen van een volledige solution, incl de embedded features 
	 * 
	 * @return een gekloonde solution
	 */
	public PartialSolution clone() {
		PartialSolution s = new PartialSolution();
		
		s.areaToCover = areaToCover;
		s.total_coverage = total_coverage;
		
		s.antennaslength = antennaslength;
		s.loglevels = loglevels;
		s.parent = parent;
		s.antennas = new Antenna[antennaslength];
		
		s.geometry = new Geometry[loglevels][antennaslength+1]; //+1 voor nullpointers te voorkomen.  In toekomst macht van 2 prolly
		
		for (int i = 0; i < antennaslength; i++) {
			if (antennas[i] != null) 
				s.antennas[i] = antennas[i].clone();
			else
				s.antennas[i] = null;
			for (int j = 0; j < loglevels; j++)
				s.geometry[j][i] 	= geometryFactory.createGeometry(geometry[j][i]);
		}
		s.coverageFitness = coverageFitness;
		s.costFitness = costFitness;	
		return s;
	}
	
	
	
	/**
	 * UndoMove Klasse voor 1 zet terug te gaan
	 * Zorgt voor enorme speedup door selectieve backup & restore
	 * 
	 * @author Bart Moens
	 */
	private class UndoMove {
		
		private int antennanumber;
		
		private Stack<Integer> indexStack;
		private Stack<Integer> levelStack;
		private Stack<Geometry> geometryStack;
		
		private double 		prevCoverageFitness;
		private Geometry	prevTotal_coverage;
		private boolean 	prevActive;
		private double 		prevRange;
		
		public UndoMove(int antennanumber,
				boolean 	prevActive,
				double 		prevCoverageFitness,
				Geometry	prevTotal_coverage,
				double 		prevRange){
			this.antennanumber = antennanumber;

			this.prevActive = prevActive;
			this.prevCoverageFitness = prevCoverageFitness;
			this.prevRange = prevRange;
			this.prevTotal_coverage = prevTotal_coverage;
			
			indexStack = new Stack<Integer>();
			levelStack = new Stack<Integer>();
			geometryStack = new Stack<Geometry>();
		}
				
		public void addTableMove(int index, int level, Geometry g) {
			this.indexStack.add(index);
			this.levelStack.add(level);
			this.geometryStack.add(geometryFactory.createGeometry(g));
		}
		
		public void undo(){
			coverageFitness = prevCoverageFitness;
			total_coverage = prevTotal_coverage;
			antennas[antennanumber].setRange(prevRange);
			antennas[antennanumber].setActive(prevActive);
			
			while (!indexStack.isEmpty()){
				int i = indexStack.pop();
				int l = levelStack.pop();
				geometry[l][i] = geometryStack.pop();
			}
		}		
	}





}